{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf  \n",
    "import midi  \n",
    "import numpy as np  \n",
    "import os  \n",
    "   \n",
    "lower_bound = 24  \n",
    "upper_bound = 102  \n",
    "span = upper_bound - lower_bound  \n",
    "   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# midi文件转Note(音符)  \n",
    "def midiToNoteStateMatrix(midi_file_path, squash=True, span=span):  \n",
    "    pattern = midi.read_midifile(midi_file_path)  \n",
    "   \n",
    "    time_left = []  \n",
    "    for track in pattern:  \n",
    "        time_left.append(track[0].tick)  \n",
    "      \n",
    "    posns = [0 for track in pattern]  \n",
    "   \n",
    "    statematrix = []  \n",
    "    time = 0  \n",
    "   \n",
    "    state = [[0,0] for x in range(span)]  \n",
    "    statematrix.append(state)  \n",
    "    condition = True  \n",
    "    while condition:  \n",
    "        if time % (pattern.resolution / 4) == (pattern.resolution / 8):  \n",
    "            oldstate = state  \n",
    "            state = [[oldstate[x][0],0] for x in range(span)]  \n",
    "            statematrix.append(state)  \n",
    "        for i in range(len(time_left)):  \n",
    "            if not condition:  \n",
    "                break  \n",
    "            while time_left[i] == 0:  \n",
    "                track = pattern[i]  \n",
    "                pos = posns[i]  \n",
    "   \n",
    "                evt = track[pos]  \n",
    "                if isinstance(evt, midi.NoteEvent):  \n",
    "                    if (evt.pitch < lower_bound) or (evt.pitch >= upper_bound):  \n",
    "                        pass  \n",
    "                    else:  \n",
    "                        if isinstance(evt, midi.NoteOffEvent) or evt.velocity == 0:  \n",
    "                            state[evt.pitch-lower_bound] = [0, 0]  \n",
    "                        else:  \n",
    "                            state[evt.pitch-lower_bound] = [1, 1]  \n",
    "                elif isinstance(evt, midi.TimeSignatureEvent):  \n",
    "                    if evt.numerator not in (2, 4):  \n",
    "                        out =  statematrix  \n",
    "                        condition = False  \n",
    "                        break  \n",
    "                try:  \n",
    "                    time_left[i] = track[pos + 1].tick  \n",
    "                    posns[i] += 1  \n",
    "                except IndexError:  \n",
    "                    time_left[i] = None  \n",
    "   \n",
    "            if time_left[i] is not None:  \n",
    "                time_left[i] -= 1  \n",
    "   \n",
    "        if all(t is None for t in time_left):  \n",
    "            break  \n",
    "   \n",
    "        time += 1  \n",
    "   \n",
    "    S = np.array(statematrix)  \n",
    "    statematrix = np.hstack((S[:, :, 0], S[:, :, 1]))  \n",
    "    statematrix = np.asarray(statematrix).tolist()  \n",
    "    return statematrix  \n",
    "   \n",
    "# Note转midi文件  \n",
    "def noteStateMatrixToMidi(statematrix, filename=\"output_file\", span=span):  \n",
    "    statematrix = np.array(statematrix)  \n",
    "    if not len(statematrix.shape) == 3:  \n",
    "        statematrix = np.dstack((statematrix[:, :span], statematrix[:, span:]))  \n",
    "    statematrix = np.asarray(statematrix)  \n",
    "    pattern = midi.Pattern()  \n",
    "    track = midi.Track()  \n",
    "    pattern.append(track)  \n",
    "      \n",
    "    span = upper_bound-lower_bound  \n",
    "    tickscale = 55  \n",
    "      \n",
    "    lastcmdtime = 0  \n",
    "    prevstate = [[0,0] for x in range(span)]  \n",
    "    for time, state in enumerate(statematrix + [prevstate[:]]):    \n",
    "        offNotes = []  \n",
    "        onNotes = []  \n",
    "        for i in range(span):  \n",
    "            n = state[i]  \n",
    "            p = prevstate[i]  \n",
    "            if p[0] == 1:  \n",
    "                if n[0] == 0:  \n",
    "                    offNotes.append(i)  \n",
    "                elif n[1] == 1:  \n",
    "                    offNotes.append(i)  \n",
    "                    onNotes.append(i)  \n",
    "            elif n[0] == 1:  \n",
    "                onNotes.append(i)  \n",
    "        for note in offNotes:  \n",
    "            track.append(midi.NoteOffEvent(tick=(time-lastcmdtime)*tickscale, pitch=note+lower_bound))  \n",
    "            lastcmdtime = time  \n",
    "        for note in onNotes:  \n",
    "            track.append(midi.NoteOnEvent(tick=(time-lastcmdtime)*tickscale, velocity=40, pitch=note+lower_bound))  \n",
    "            lastcmdtime = time  \n",
    "              \n",
    "        prevstate = state  \n",
    "      \n",
    "    eot = midi.EndOfTrackEvent(tick=1)  \n",
    "    track.append(eot)  \n",
    "   \n",
    "    midi.write_midifile(\"{}.mid\".format(filename), pattern)  \n",
    "\n",
    "# 读取midi数据  \n",
    "def get_songs(midi_path):  \n",
    "    files = os.listdir(midi_path)  \n",
    "    songs = []  \n",
    "    for f in files:  \n",
    "        f = midi_path+'/'+f  \n",
    "        print('加载:', f)  \n",
    "        try:  \n",
    "            song = np.array(midiToNoteStateMatrix(f))  \n",
    "            if np.array(song).shape[0] > 64:  \n",
    "                songs.append(song)  \n",
    "        except Exception as e:  \n",
    "            print('数据无效: ', e)  \n",
    "    print(\"读取的有效midi文件个数: \", len(songs))  \n",
    "    return songs  \n",
    "   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "加载: midi/Titantic.mid\n",
      "加载: midi/YoureBeautiful.mid\n",
      "加载: midi/PokerFace.mid\n",
      "加载: midi/MyHumps.mid\n",
      "加载: midi/HotelCalifornia.mid\n",
      "读取的有效midi文件个数:  5\n",
      "0\n",
      "1\n",
      "2\n",
      "3\n",
      "4\n",
      "5\n",
      "6\n",
      "7\n",
      "8\n",
      "9\n",
      "10\n",
      "11\n",
      "12\n",
      "13\n",
      "14\n",
      "15\n",
      "16\n",
      "17\n",
      "18\n",
      "19\n",
      "20\n",
      "21\n",
      "22\n",
      "23\n",
      "24\n",
      "25\n",
      "26\n",
      "27\n",
      "28\n",
      "29\n",
      "30\n",
      "31\n",
      "32\n",
      "33\n",
      "34\n",
      "35\n",
      "36\n",
      "37\n",
      "38\n",
      "39\n",
      "40\n",
      "41\n",
      "42\n",
      "43\n",
      "44\n",
      "45\n",
      "46\n",
      "47\n",
      "48\n",
      "49\n",
      "50\n",
      "51\n",
      "52\n",
      "53\n",
      "54\n",
      "55\n",
      "56\n",
      "57\n",
      "58\n",
      "59\n",
      "60\n",
      "61\n",
      "62\n",
      "63\n",
      "64\n",
      "65\n",
      "66\n",
      "67\n",
      "68\n",
      "69\n",
      "70\n",
      "71\n",
      "72\n",
      "73\n",
      "74\n",
      "75\n",
      "76\n",
      "77\n",
      "78\n",
      "79\n",
      "80\n",
      "81\n",
      "82\n",
      "83\n",
      "84\n",
      "85\n",
      "86\n",
      "87\n",
      "88\n",
      "89\n",
      "90\n",
      "91\n",
      "92\n",
      "93\n",
      "94\n",
      "95\n",
      "96\n",
      "97\n",
      "98\n",
      "99\n",
      "100\n",
      "101\n",
      "102\n",
      "103\n",
      "104\n",
      "105\n",
      "106\n",
      "107\n",
      "108\n",
      "109\n",
      "110\n",
      "111\n",
      "112\n",
      "113\n",
      "114\n",
      "115\n",
      "116\n",
      "117\n",
      "118\n",
      "119\n",
      "120\n",
      "121\n",
      "122\n",
      "123\n",
      "124\n",
      "125\n",
      "126\n",
      "127\n",
      "128\n",
      "129\n",
      "130\n",
      "131\n",
      "132\n",
      "133\n",
      "134\n",
      "135\n",
      "136\n",
      "137\n",
      "138\n",
      "139\n",
      "140\n",
      "141\n",
      "142\n",
      "143\n",
      "144\n",
      "145\n",
      "146\n",
      "147\n",
      "148\n",
      "149\n",
      "150\n",
      "151\n",
      "152\n",
      "153\n",
      "154\n",
      "155\n",
      "156\n",
      "157\n",
      "158\n",
      "159\n",
      "160\n",
      "161\n",
      "162\n",
      "163\n",
      "164\n",
      "165\n",
      "166\n",
      "167\n",
      "168\n",
      "169\n",
      "170\n",
      "171\n",
      "172\n",
      "173\n",
      "174\n",
      "175\n",
      "176\n",
      "177\n",
      "178\n",
      "179\n",
      "180\n",
      "181\n",
      "182\n",
      "183\n",
      "184\n",
      "185\n",
      "186\n",
      "187\n",
      "188\n",
      "189\n",
      "190\n",
      "191\n",
      "192\n",
      "193\n",
      "194\n",
      "195\n",
      "196\n",
      "197\n",
      "198\n",
      "199\n",
      "200\n",
      "201\n",
      "202\n",
      "203\n",
      "204\n",
      "205\n",
      "206\n",
      "207\n",
      "208\n",
      "209\n",
      "210\n",
      "211\n",
      "212\n",
      "213\n",
      "214\n",
      "215\n",
      "216\n",
      "217\n",
      "218\n",
      "219\n",
      "220\n",
      "221\n",
      "222\n",
      "223\n",
      "224\n",
      "225\n",
      "226\n",
      "227\n",
      "228\n",
      "229\n",
      "230\n",
      "231\n",
      "232\n",
      "233\n",
      "234\n",
      "235\n",
      "236\n",
      "237\n",
      "238\n",
      "239\n",
      "240\n",
      "241\n",
      "242\n",
      "243\n",
      "244\n",
      "245\n",
      "246\n",
      "247\n",
      "248\n",
      "249\n",
      "250\n",
      "251\n",
      "252\n",
      "253\n",
      "254\n",
      "255\n",
      "生成auto_gen_music.mid文件\n"
     ]
    }
   ],
   "source": [
    "# midi目录中包含了下载的midi文件  \n",
    "songs = get_songs('midi')  \n",
    "   \n",
    "note_range = upper_bound - lower_bound  \n",
    "# 音乐长度  \n",
    "n_timesteps = 128  \n",
    "n_input = 2 * note_range * n_timesteps  \n",
    "n_hidden = 64  \n",
    "   \n",
    "X = tf.placeholder(tf.float32, [None, n_input])  \n",
    "W = None  \n",
    "bh = None  \n",
    "bv = None  \n",
    "   \n",
    "def sample(probs):  \n",
    "    return tf.floor(probs + tf.random_uniform(tf.shape(probs), 0, 1))  \n",
    "   \n",
    "def gibbs_sample(k):  \n",
    "    def body(count, k, xk):  \n",
    "        hk = sample(tf.sigmoid(tf.matmul(xk, W) + bh))  \n",
    "        xk = sample(tf.sigmoid(tf.matmul(hk, tf.transpose(W)) + bv))  \n",
    "        return count+1, k, xk  \n",
    "   \n",
    "    count = tf.constant(0)  \n",
    "    def condition(count,  k, xk):  \n",
    "        return count < k  \n",
    "    [_, _, x_sample] = tf.while_loop(condition, body, [count, tf.constant(k), X])  \n",
    "   \n",
    "    x_sample = tf.stop_gradient(x_sample)   \n",
    "    return x_sample  \n",
    "   \n",
    "#定义神经网络  \n",
    "def neural_network():  \n",
    "    global W  \n",
    "    W  = tf.Variable(tf.random_normal([n_input, n_hidden], 0.01))  \n",
    "    global bh  \n",
    "    bh = tf.Variable(tf.zeros([1, n_hidden],  tf.float32))  \n",
    "    global bv  \n",
    "    bv = tf.Variable(tf.zeros([1, n_input],  tf.float32))  \n",
    "   \n",
    "    x_sample = gibbs_sample(1)  \n",
    "    h = sample(tf.sigmoid(tf.matmul(X, W) + bh))  \n",
    "    h_sample = sample(tf.sigmoid(tf.matmul(x_sample, W) + bh))  \n",
    "   \n",
    "    learning_rate = tf.constant(0.005, tf.float32)  \n",
    "    size_bt = tf.cast(tf.shape(X)[0], tf.float32)  \n",
    "    W_adder  = tf.multiply(learning_rate/size_bt, tf.subtract(tf.matmul(tf.transpose(X), h), tf.matmul(tf.transpose(x_sample), h_sample)))  \n",
    "    bv_adder = tf.multiply(learning_rate/size_bt, tf.reduce_sum(tf.subtract(X, x_sample), 0, True))  \n",
    "    bh_adder = tf.multiply(learning_rate/size_bt, tf.reduce_sum(tf.subtract(h, h_sample), 0, True))  \n",
    "    update = [W.assign_add(W_adder), bv.assign_add(bv_adder), bh.assign_add(bh_adder)]  \n",
    "    return update  \n",
    "   \n",
    "# 训练神经网络  \n",
    "def train_neural_network():  \n",
    "    update = neural_network()  \n",
    "   \n",
    "    with tf.Session() as sess:  \n",
    "        sess.run(tf.global_variables_initializer())  \n",
    "   \n",
    "        saver = tf.train.Saver(tf.all_variables())  \n",
    "   \n",
    "        epochs = 256  \n",
    "        batch_size = 64  \n",
    "        for epoch in range(epochs):  \n",
    "            for song in songs:  \n",
    "                song = np.array(song)  \n",
    "                song = song[:int(np.floor(song.shape[0]/n_timesteps) * n_timesteps)]  \n",
    "                song = np.reshape(song, [song.shape[0]//n_timesteps, song.shape[1] * n_timesteps])  \n",
    "          \n",
    "            for i in range(1, len(song), batch_size):   \n",
    "                train_x = song[i:i+batch_size]  \n",
    "                sess.run(update, feed_dict={X: train_x})  \n",
    "            print(epoch)  \n",
    "            # 保存模型  \n",
    "            if epoch == epochs - 1:  \n",
    "                saver.save(sess, os.path.join(os.getcwd(), 'midi.module'))\n",
    "   \n",
    "        # 生成midi  \n",
    "        sample = gibbs_sample(1).eval(session=sess, feed_dict={X: np.zeros((1, n_input))})  \n",
    "        S = np.reshape(sample[0,:], (n_timesteps, 2 * note_range))  \n",
    "        noteStateMatrixToMidi(S, \"auto_gen_music\")  \n",
    "        print('生成auto_gen_music.mid文件')  \n",
    "\n",
    "train_neural_network()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
